// ###########################################################################################################################
// Miscellaneous functions not specific to any common category.
// ###########################################################################################################################

module DreamSpace.System.Utilities {
    // -------------------------------------------------------------------------------------------------------------------

    export var FUNC_NAME_REGEX = /^function\s*(\S+)\s*\(/i; // (note: never use the 'g' flag here, or '{regex}.exec()' will only work once every two calls [attempts to traverse])

    /** Attempts to pull the function name from the function text, and returns 'undefined' for anonymous functions. */
    export function getFunctionName(func: Function): string {
        var name = (<ITypeInfo><any>func).$__name || func['name'];
        if (name == void 0) {
            var fstr = func.toString();
            var results = (FUNC_NAME_REGEX).exec(fstr); // (note: for function expression object contexts, the constructor (type) name is always 'Function')
            name = (results && results.length > 1) ? results[1] : UNDEFINED;
        }
        return name;

    }

    // -------------------------------------------------------------------------------------------------------------------

    /** This locates names of properties where only a reference and the object context is known.
    * If a reference match is found, the property name is returned, otherwise the result is 'undefined'.
    */
    export function getReferenceName(obj: {}, reference: {}): string {
        for (var p in obj)
            if (obj[p] === reference) return p;
        return UNDEFINED;
    }

    // -------------------------------------------------------------------------------------------------------------------

    /** Erases all properties on the object, instead of deleting them (which takes longer).
    * @param {boolean} release If false, then care is taken not to erase any property that contains a 'dispose()' function. (default: true)
    *                          This is provided to support reconstructing nested object groups without needing to rebuild the associations.
    */
    export function erase(obj: {}, release = true): {} {
        for (var p in obj)
            if ((p != "__proto__" && p != 'constructor' && <NativeTypes.IObject>obj).hasOwnProperty(p))
                if (release || p == 'dispose' && typeof obj[p] != 'function')
                    obj[p] = void 0;
        return obj;
    }

    /** Makes a deep copy of the specified value and returns it. If the value is not an object, it is returned immediately.
    * For objects, the deep copy is made by */
    export function clone(value) {
        if (typeof value !== 'object') return value;
        var newObject: {}, p: string, rcCount: number, v;
        if (clone.arguments.length > 1) {
            rcCount = clone.arguments[clone.arguments.length - 1];
            if (value['@__recursiveCheck'] === rcCount) return value; // (this object has already been cloned for this request, which makes it a cyclical reference, so skip)
        }
        else rcCount = (value['@__recursiveCheck'] || 0) + 1; // (initially, rcCount will be set to the root __recursiveCheck value, +1, rather than re-creating all properties over and over for each clone request [much faster]) 
        value['@__recursiveCheck'] = rcCount;
        newObject = {};
        for (p in value) { // (note: not using "hasOwnProperty()" here because replicating any inheritance is not supported (nor usually needed), so all properties will be flattened for the new object instance)
            v = value[p];
            if (typeof v !== 'object')
                newObject[p] = v; // (faster to test and set than to call a function)
            else
                newObject[p] = (<Function>clone)(v, rcCount);
        }
        return newObject;
    };

    // -------------------------------------------------------------------------------------------------------------------

    /** Dereferences a property path in the form "A.B.C..." and returns the right most property value, if exists, otherwise
    * 'undefined' is returned (i.e. the undefined value). */
    export function dereferencePropertyPath(obj: {}, propertyPath: string): {} {
        var i = 0, properties = propertyPath.split('.');
        while (obj !== void 0 && i < properties.length)
            obj = obj[properties[i++]];
        return obj;
    }

    // -------------------------------------------------------------------------------------------------------------------

    /** Waits until a property of an object becomes available (i.e. is no longer 'undefined').
      * @param {Object} obj The object for the property.
      * @param {string} propertyName The object property.
      * @param {number} timeout The general amount of timeout to wait before failing, or a negative value to wait indefinitely.
      */
    export function waitReady(obj: {}, propertyName: string, callback: Function, timeout: number = 60000, timeoutCallback?: Function) {
        if (!callback) throw "'callback' is required.";
        if (!obj) throw "'obj' is required.";
        if (obj[propertyName] !== void 0)
            callback();
        else {
            if (timeout != 0) {
                if (timeout > 0) timeout--;
                setTimeout(() => {
                    waitReady(obj, propertyName, callback);
                }, 1);
            }
            else if (timeoutCallback)
                timeoutCallback();
        }
    }

    // -------------------------------------------------------------------------------------------------------------------

    /** Extends a based type prototype by chaining a derived type's 'prototype' to the base type's prototype.
    * Note: Extending an already extended prototype will recreate the prototype connection again, pointing it to the new prototype.
    * Note: It is not possible to modify any existing chain of constructor calls.  Only the prototype can be changed.
    * @param {Function} derivedType The derived type (function) that will extend from a base type.
    * @param {Function} baseType The base type (function) to extend to the derived type.
    * @param {boolean} copyBaseProperties If true (default) behaves like the TypeScript "__extends" method, which copies forward any static base properties to the derived type.
    */
    export function extend<DerivedType extends Function, BaseType extends Function>(derivedType: DerivedType, baseType: BaseType, copyBaseProperties: boolean = true): DerivedType {
        // ... create a prototype link for the given type ...
        function __() { this.constructor = derivedType; }
        __.prototype = baseType.prototype;
        // ... copy any already defined properties in the derived prototype to be replaced, if any ...
        var newProto = new __();
        for (var p in derivedType.prototype)
            if (derivedType.prototype.hasOwnProperty(p))
                newProto[p] = derivedType.prototype[p];
        // ... set the new prototype ...
        derivedType.prototype = newProto;
        // ... finally, copy forward any static properties ...
        if (copyBaseProperties)
            for (var p in baseType)
                if (baseType.hasOwnProperty(p))
                    derivedType[p] = baseType[p];
        // ... return the extended derived type ...
        return derivedType;
    };

    // -------------------------------------------------------------------------------------------------------------------

    /** Helps support cases where 'apply' is missing for a host function object (i.e. IE7 'setTimeout', etc.).  This function
    * will attempt to call '.apply()' on the specified function, and fall back to a work around if missing.
    * @param {Function} func The function to call '.apply()' on.
    * @param {Object} _this The calling object, which is the 'this' reference in the called function (the 'func' argument).
    * Note: This must be null for special host functions, such as 'setTimeout' in IE7.
    * @param {any} args The arguments to apply to given function reference (the 'func' argument).
    */
    export function apply(func: Function, _this: Object, args: any[]): any {
        if (func.apply) {
            return func.apply(_this, args);
        } else {
            return Function.prototype.apply.apply(func, [_this, args]);
        }
    }

    // -------------------------------------------------------------------------------------------------------------------
}

// ###########################################################################################################################
